package com.cy.pj.sys.Service;

import com.cy.pj.sys.commom.annotation自定义注解类.RequiredLog;
import com.cy.pj.sys.pojo.SysLog;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import sun.util.calendar.BaseCalendar;

import java.lang.reflect.Method;
import java.util.Date;


/**
 * 在Spring AOP应用中，基于@Aspect注解描述的类型为一个切面类型，
 * 此类中要封装切入点及通知方法的定义
 * 1)切入点：要切入扩展业务逻辑的一些目标方法集合(例如使用了特点注解描述的方法)
 *2)通知：封装了扩展业务逻辑的一个方法(Spring中也可使用特定注解进行描述)
 */
@Order(1)
@Slf4j
@Aspect
@Component //组件类
public class SysLogAspect切面AOP {


    /**
     * 基于@Pointcut注解定义切入点，这里的@annotation为一种切入点表达式，
     * 表示由RequiredLog注解描述的方法为一个切入点方法，我们要在这样的
     * 方法上添加扩展业务逻辑
     */
    @Pointcut("@annotation(com.cy.pj.sys.commom.annotation自定义注解类.RequiredLog)")
    public void doLog() {
    } //此方法用于承载切入点的定义

    /**
     * 定义一个通知方法，这里使用@Around进行描述，表示再次方法内部可以调用
     * 目标方法，可以在目标方法执行之前或之后做一些拓展业务
     *
     * @param jp 连接点，用于封装要执行的目标切入点方法信息，
     *           ProceedingJoinPoint-此类型参数只能定义在@Around注解描述的方法中
     * @return 这里的返回值一般为目标方法的执行结果
     */
    @Around("doLog()")
    public Object daAround(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("SysLogAspect.doAround.before");
        long t1 = System.currentTimeMillis();
        log.info("method start {}", t1);
        try {
            Object result = jp.proceed(); //反射调用，通过连接点调用目标方法(method.invoke方法的回调)
            long t2 = System.currentTimeMillis();
            log.info("method after {}", t2);
            //记录用户正常行为日志
            saveLogInfo(jp, (t2 - t1), null);//SpringBoot线程池中的线程
            return result;
        } catch (Throwable e) {
            log.error("method error {}", System.currentTimeMillis());
            long t3 = System.currentTimeMillis();
            saveLogInfo(jp, (t3 - t1), e);
            throw e;
        }
    }

    @Autowired
    private SysLogService sysLogService;

    private void saveLogInfo(ProceedingJoinPoint jp, long time, Throwable e) throws NoSuchMethodException, JsonProcessingException {
        //1.获取用户行为日志
        //1.1获取登录用户信息
        String username = "tony";//后续是系统的登录用户
        //1.2获取登录用户的ip地址
        String ip = "192.168.100.11";//后续可以借助三方工具类
        //1.3获取用户操作
        //1.3.1获取方法所在类的字节码对象(目标对象对应的字节码对象)
        //jp是连接点参数，就是切入点方法，jp的字节码对象就是 业务实现类切入点方法的字节码对象
        Class<?> cls = jp.getTarget().getClass();
        //1.3.2获取含有注解描述的方法的对象(字节码对象，方法名，参数列表)
        Signature signature = jp.getSignature();
//signature指向的是变量类型--MethodSignatureImpl,但它是某一个类里面私有的内部类，所以要强转
        MethodSignature methodSignature = (MethodSignature) signature;
        //只适用于CGLiB代理对象：Method targetMethod=methodSignature.getMethod();
        Method targetMethod = //适用于CGLiB和JDK的代理对象
                //通过反射，获取方法名和参数类型列表
                cls.getDeclaredMethod(methodSignature.getName(), methodSignature.getParameterTypes());
        //1.3.3获取RequiredLog注解
        RequiredLog requiredLog = targetMethod.getAnnotation(RequiredLog.class);
        String operation = requiredLog.operation();
        //1.4获取方法声明信息(类名+方法名)
        String method = cls.getName() + "." + targetMethod.getName();
        //1.5获取方法执行时传入的实际参数
        Object[] args = jp.getArgs();//实际的参数
        //把参数转化为 json字符串,以后提取方便
        String params = new ObjectMapper().writeValueAsString(args);
        //1.6获取状态信息
        Integer status = e == null ? 1 : 0;//1表示ok
        //1.7获取错误信息
        String error = e == null ? " " : e.getMessage();

        //2.封装用户行为日志
        SysLog sysLog = new SysLog();

        //createtime不用下面的方式，直接在xml中--now()
        //  sysLog.setCreatedTime(new java.sql.Date(createdtime.getTime()));
        sysLog.setOperation(operation);
        sysLog.setStatus(status);
        sysLog.setError(error);
        sysLog.setUsername(username);
        sysLog.setIp(ip);
        sysLog.setMethod(method);
        sysLog.setParams(params);
        sysLog.setTime(time);
        //将日志写入到数据库
        log.info("user log {}", new ObjectMapper().writeValueAsString(sysLog));

        sysLogService.insertLogService(sysLog);
        //new Thread() {//1个线程，内存默认给1M
          //  @Override
            //public void run() {
              //  sysLogService.insertLogService(sysLog);
            //}
        //}.start();
    }
}